<?php
/**
 * PingAn 平安银行跨行支付组件
 *
 * @author lizaifang <lizf@wanthings.com>
 * @version 0.4.0
 * @since 2017/8/21 10:39
 * @changelog
 * 0.10.0 完成对账数据查询
 * 0.9.0 完成批量查询订单结果
 * 0.8.0 完成退款查询
 * 0.7.0 完成退款
 * 0.6.0 完成验签
 * 0.5.0 完成支付结果查询
 * 0.4.0 完成支付结果通知
 * 0.3.0 完成支付请求
 * 0.2.0 完成加解密
 * 0.1.0 完成证书解析
 */
class PingAn
{
//    const pay_URL = 'https://testebank.sdb.com.cn/khpayment/khPayment.do';
    const PAY_URL = 'https://my-uat1.orangebank.com.cn/corporbank/khPayment.do';

    public $merchant_no = '';
    public $merchant_cert_path = '';
    public $merchant_cert_pass = '';
    public $merchant_cert_info = null;
    public $merchant_cert_private_key = null;
    public $pingan_cert_path = '';
    public $pingan_cert_public_key = '';
    public $pingan_cert_info = null;

    private $_errors = array();

    public function __construct($merchant_params, $debug = false)
    {
        $this->merchant_no = $merchant_params['merchant_no'];
        $this->merchant_cert_path = $merchant_params['merchant_cert_path'];
        $this->merchant_cert_pass = $merchant_params['merchant_cert_pass'];
        $this->pingan_cert_public_key = $merchant_params['pingan_cert_public_key'];

        // read pkey into mem
        $this->parseCert();
    }

    public function buildPayInfoWeb($order_info, $extra = array())
    {
        $request = array();
        $request['masterId'] = $this->merchant_no;
        $request['orderId'] = $this->merchant_no . $order_info['order_sn'];
        $request['currency'] = 'RMB';
        $request['amount'] = $order_info['amount'];
        $request['objectName'] = iconv("UTF-8", "GBK", $order_info['product_name']);//varchar 200
        $request['paydate'] = date('YmdHis', $order_info['order_time']); // 下单时间YYYYMMDDHHMMSS
        $request['validtime'] = 86400 * 2 * 1000; //订单有效期(毫秒)，0不生效
        if (isset($order_info['remark'])) {
            $request['remark'] = $order_info['remark'];
        }

        $result = array();
        $result['url'] = self::PAY_URL;
        $result['method'] = 'POST';
        $result['params'] = array_merge(
            $this->buildTrans($request), array(
                'returnurl' => $order_info['return_url'],
                'NOTIFYURL' => $order_info['notify_url'],
            )
        );
        return $result;
    }

    private function splitResponse($content)
    {
        $response_data = explode('orig=', $content);
        $tmp = explode('sign=', $response_data[0]);// sign=xxx
        $sign = $tmp[1];

        $tmp2 = explode('SDBPAYGATE=', $response_data[1]);// orig=xxx

        $orig = $tmp2[0];
//        var_dump($sign);
//        var_dump($orig);
        return array($orig, $sign);
    }

    //3.2单笔订单状态查询接口（KH0001）
    public function query($params)
    {
        $url = "https://my-uat1.orangebank.com.cn/corporbank/KH0001.pay";
        $request = array();
        $request['masterId'] = $this->merchant_no;
        $request['orderId'] = $this->merchant_no . $params['order_sn'];

        $data = $this->buildTrans($request);
        $result = $this->request($url, $data, 'POST');
//        var_dump($result);

        list($orig, $sign) = $this->splitResponse($result);

        // TODO: verify cert
        $this->verifyCert($orig, $sign);
        if (true) {
//            var_dump($orig);
//            var_dump($sign);

            $data_url = urldecode($orig); // url
            $orig_xml = base64_decode($data_url); // base64
//            var_dump($orig_xml);

            $response = $this->xmlToArray($orig_xml);
//            var_dump($response);
//            errorCode	varchar	8	错误返回相应的错误码, 正常返回为空
//            errorMsg	varchar	100	错误码对应的错误说明，正常返回为空，
//            status	char	2	‘01’表成功，只返回成功
//            date	varchar	14	支付清算日期，可为空
//            charge	number	12,2	订单手续费金额，12整数，2小数
//            masterId	char	10	收费企业ID
//            orderId	varchar	26	ID+YYYYMMDD+8位流水
//            currency	char	3	目前只支持RMB
//            amount	number	12,2	订单金额，最大12位整数，2位小数
//            objectName	varchar	200	款项描述（不可含分割符）
//            paydate	varchar	14	YYYYMMDDHHMMSS
//            validtime	number	10	订单有效期(秒)，0不生效
//            remark	varchar	500	备注字段（不可含分割符）
//            settleflg	char	1	订单本金清算 标志‘1’已清算，‘0’待清算
//            settletime	varchar	14	本金清算时间 YYYYMMDDHHMMSS
//            chargeflg	char	1	手续费清算标志‘1’已清算，‘0’待清算
//            chargetime	varchar	14	手续费清算时间 YYYYMMDDHHMMSS
            if (empty($response['errorCode'])) {
                if ($response['masterId'] != $this->merchant_no) {
                    $this->setErrors(array(403, '商户配置不正确'));
                    return;
                }
                $result = array();
                $result['order_sn'] = trim($response['orderId'], $this->merchant_no);
                $result['amount'] = $response['amount'];
                $result['pay_time'] = $response['date'];
                $result['custom'] = isset($response['remark']) ? $response['remark'] : '';
                $result['raw'] = $response;
                return $result;
            } else {
                $this->setErrors(array($response['errorCode'], $response['errorMsg']));
                return;
            }
        } else {
            $this->setErrors(array(403, 'cert not verify'));
        }
    }

    //3.3订单列表信息查询接口（KH0002）
    public function queryByDate($begin_datetime, $end_datetime)
    {
        $url = "https://my-uat1.orangebank.com.cn/corporbank/KH0002.pay";
//        masterId	char	10	商户ID
//        beginDate	varchar	14	查询开始日期（支付完成日期）
//        endDate	varchar	14	查询结束日期（支付完成日期）
        $request = array();
        $request['masterId'] = $this->merchant_no;
        $request['beginDate'] = date('YmdHis', $begin_datetime);
        $request['endDate'] = date('YmdHis', $end_datetime);

        $data = $this->buildTrans($request);
        $result = $this->request($url, $data, 'POST');
        var_dump($result);
    }

    //reconcile
    //3.4每日对账单查询接口（KH0003）
    public function reconciliation($date)
    {
        $url = "https://my-uat1.orangebank.com.cn/corporbank/KH0003.pay";
//        masterId	char	10	商户ID
//        date 	varchar 	8 	对账日期，格式：YYYYMMDD
        $request = array();
        $request['masterId'] = $this->merchant_no;
        $request['date'] = date('Ymd', $date);

        $data = $this->buildTrans($request);
        $result = $this->request($url, $data, 'POST');
        var_dump($result);
    }

    //3.5单笔订单退款接口（KH0005）
    public function refund($order_info, $extra = array())
    {
        $url = "https://my-uat1.orangebank.com.cn/corporbank/KH0005.pay";
//        masterId	char	10	是	商户号，注意生产环境上要替换成商户自己的生产商户号
//        refundNo	varchar	26	是	退款单号，严格遵守格式：商户号+8位日期YYYYMMDD+8位流水
//        orderId	varchar	26	是	原订单号，严格遵守格式：商户号+8位日期YYYYMMDD+8位流水
//        currency	char	3	是	币种，目前只支持RMB
//        refundAmt	number	12,2	是	退款金额，12整数，2小数，必须等于原订单金额
//        objectName	varchar	200	否	退款原因（商户自定）
//        remark	varchar	500	否	备注字段（商户自定）
//        NOTIFYURL	varchar	100	是	商户接收订单退款结果服务器异步通知的url，支付平台通知银行，则银行通知商户，否则不通知。
        $request = array();
        $request['masterId'] = $this->merchant_no;
        $request['refundNo'] = $this->merchant_no . $order_info['refund_sn'];
        $request['orderId'] = $this->merchant_no . $order_info['order_sn'];
        $request['currency'] = 'RMB';
        $request['refundAmt'] = $order_info['refund_amount'];
        if (isset($order_info['refund_reason'])) {
            $request['objectName'] = $order_info['refund_reason'];
        }
        if (isset($order_info['remark'])) {
            $request['remark'] = $order_info['remark'];
        }
        $request['NOTIFYURL'] = $order_info['notify_url'];

        $data = $this->buildTrans($request);
        $result = $this->request($url, $data, 'POST');
        var_dump($result);
    }

    //3.6订单退款查询接口（KH0006）
    public function queryRefund($begin_datetime, $end_datetime)
    {
        $url = "https://my-uat1.orangebank.com.cn/corporbank/KH0006.pay";
//        masterId	char	10	是	商户号，注意生产环境上要替换成商户自己的生产商户号
//        beginDate	varchar	14	是	查询开始时间（退款请求接收时间）YYYYMMDDHHMMSS
//        endDate	varchar	14	是	查询结束时间（退款请求接收时间）YYYYMMDDHHMMSS
        $request = array();
        $request['masterId'] = $this->merchant_no;
        $request['beginDate'] = date('YmdHis', $begin_datetime);
        $request['endDate'] = date('YmdHis', $end_datetime);

        $data = $this->buildTrans($request);
        $result = $this->request($url, $data, 'POST');
        var_dump($result);
    }

    public function verifyNotify($params = array(), $is_return = false)
    {
        // 0. 获取全部参数, 并保存下来
        // 1. 抽取数据
        // 2. 验证签名
        // 3. 判断支付结果
        // 4. 返回订单相关信息 或者 设置错误码
        $result = array();
        if (empty($params)) {
            $params = array();
            $params['server'] = $_SERVER;
            $params['post'] = $_POST;
            $params['get'] = $_GET;
            $params['request'] = $_REQUEST;
            $params['raw'] = file_get_contents("php://input");
            file_put_contents(Yii::app()->getRuntimePath() . '/notify_' . date("Y_W") . '.txt', sprintf("%s %s %s %s", date("Y-m-d H:i:s"), $type, json_encode($params), PHP_EOL), FILE_APPEND);
        }

        // if not return params first urldecode
        if ($is_return) {
            $orig = urldecode($params['post']['orig']);
            $sign = urldecode($params['post']['sign']);
        } else {
            $orig = $params['post']['orig'];
            $sign = $params['post']['sign'];
        }
        $orig = base64_decode($orig);
        $sign = base64_decode($sign);

        if (!$this->verifyCert($orig, $sign)) {
//            $this->setErrors(array(403, '非法请求, 签名验证失败'));
//            return;
        }
        // TODO: 解码
        $response = $this->xmlToArray($orig);
//        status	char	2	订单状态，只返回‘01’，表示成功
//        date	varchar	14	支付完成时间，
//        YYYYMMDDHHMMSS
//        charge	number	12,2	订单手续费金额，12整数，2小数
//        masterId	char	10	商户号
//        orderId	varchar	26	订单号
//        currency	char	3	币种，目前只支持RMB
//        amount	number	12,2	订单金额，12整数，2小数
//        objectName	varchar	200	款项描述
//        paydate	varchar	14	下单时间，
//        YYYYMMDDHHMMSS
//        validtime	number	10	订单有效期(秒)，0不生效
//        remark	varchar	500	备注字段
        if ($this->merchant_no != $response['masterId']) {
//            $this->setErrors(array(403, '非法请求, 商户配置不正确'));
//            return;
        }
        if ($response['status'] == '01') {
            $result['order_sn'] = trim($response['orderId'], $this->merchant_no);
            $result['amount'] = $response['amount'];
            $result['pay_time'] = $response['date'];
            $result['custom'] = isset($response['remark']) ? $response['remark'] : '';
            $result['raw'] = $response;
        } else {
            $this->setErrors(array(401, '支付未成功'));
        }
        return $result;
    }

    private function verifyCert($orig, $sign)
    {
        $hexdata = trim($sign);
        $response_sign = '';
        $length = strlen($hexdata);
        for ($i = 0; $i < $length; $i += 2) {
            $response_sign .= chr(hexdec(substr($hexdata, $i, 2)));
        }
        return openssl_verify(trim($orig), $response_sign, $this->pingan_cert_public_key, OPENSSL_ALGO_MD5);
    }

    private function xmlAttribute($object, $attribute)
    {
        if (isset($object[$attribute])) {
            return (string)$object[$attribute];
        }
    }


    private function parseCert()
    {
        // 解析商户证书
        $cert_file_content = file_get_contents($this->merchant_cert_path);
        $success = openssl_pkcs12_read($cert_file_content, $this->merchant_cert_info, $this->merchant_cert_pass);
//        print_r($this->merchant_cert_info);
        $this->merchant_cert_private_key = openssl_pkey_get_private($this->merchant_cert_info['pkey']);

        $cert_file_content = file_get_contents($this->pingan_cert_public_key);
        // 解析平安证书
        $cert_pem_content = "-----BEGIN CERTIFICATE-----\n" . chunk_split(base64_encode($cert_file_content), 64, "\n") . "-----END CERTIFICATE-----\n";
        //获取key
        $this->pingan_cert_info = openssl_x509_read($cert_pem_content);
        $this->pingan_cert_public_key = openssl_pkey_get_public($this->pingan_cert_info);
    }

    private function encode($data)
    {
        $data_base64 = base64_encode($data); // base64
        $data_gbk = iconv("UTF-8", "GBK", $data_base64); // utf-8 to gbk
        $data_url = urlencode($data_gbk); // url
        return $data_url;
    }

    private function sign($data)
    {
        if (!openssl_sign($data, $signature, $this->merchant_cert_private_key, OPENSSL_ALGO_MD5)) {
            exit ("Have a error!Please check!");
        }
        $sign = bin2hex($signature);
        return $sign;
    }

    private function buildTrans($request)
    {
        $xml_data = '<kColl id="input" append="false">';
        foreach ($request as $key => $value) {
            $xml_data .= '<field id="' . $key . '" value="' . $value . '"/>';
        }
        $xml_data .= '</kColl>';

        //获取orig和sign
        $orig = $this->encode($xml_data);
        $sign = $this->sign($xml_data);
        $sign = $this->encode($sign);
        return compact('orig', 'sign');
    }

    private function request($url, $params = array(), $method = 'GET', $data_type = 'json')
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 不验证证书
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 不验证HOST
        curl_setopt($ch, CURLOPT_SSLVERSION, 1);

        if (strtoupper($method) == 'GET') {
            $query_string = http_build_query($params);
            curl_setopt($ch, CURLOPT_URL, sprintf('%s?%s', $url, $query_string));
        } else {
            if (strtolower($data_type) == 'json') {
                curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type:application/json;charset=UTF-8'));
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($params));
//                var_dump(json_encode($params));
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_URL, $url);
            } else {
                curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type:application/x-www-form-urlencoded;charset=UTF-8'));
                curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_URL, $url);
            }
        }
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        if (curl_errno($ch)) {
            $errno = curl_errno($ch);
            $errmsg = curl_error($ch);
            curl_close($ch);
            $this->setErrors(array($errno, $errmsg));
            return;
        }
        if (curl_getinfo($ch, CURLINFO_HTTP_CODE) != '200') {
            $errmsg = "http状态=" . curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);
            $this->setErrors(array($errmsg, "curl出错，错误码: $errmsg"));
            return;
        }
        curl_close($ch);
        if (true) {
//            var_dump($url);
//            var_dump($params);
//            var_dump($response);
            $notify = array();
            $notify['url'] = $url;
            $notify['params'] = $params;
            $notify['response'] = $response;
            file_put_contents(Yii::app()->getRuntimePath() . '/pingan_request_' . date("Y_W") . '.log', sprintf("%s %s %s", date("Y-m-d H:i:s"), json_encode($notify), PHP_EOL), FILE_APPEND);
        }
        return $response;
    }

    public function xmlToArray($orig_xml)
    {
        $orig_xml = iconv("GBK", "UTF-8", $orig_xml);
//        $xml = simplexml_load_string($orig_xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $xml = simplexml_load_string($orig_xml);
        $arr = json_decode(json_encode($xml), TRUE);

        $res = array();

        foreach ($arr ['field'] as $key => $row) {
            $res [$row ['@attributes'] ['id']] = $row ['@attributes'] ['value'];
            //array_push($res, $row['@attributes']['id']);
        }

        if (array_key_exists('iColl', $arr)) {
            $resBody = array();
            if (array_key_exists('kColl', $arr ['iColl'])) {
                if (!array_key_exists('field', $arr ['iColl'] ['kColl'])) {
                    foreach ($arr ['iColl'] ['kColl'] as $key => $row) {
                        $coll = array();
                        foreach ($row ['field'] as $_key => $_row) {
                            $coll [$_row ['@attributes'] ['id']] = $_row ['@attributes'] ['value'];
                        }
                        array_push($resBody, $coll);
                    }
                } else {
                    $coll = array();
                    foreach ($arr ['iColl'] ['kColl'] ['field'] as $_key => $_row) {
                        $coll [$_row ['@attributes'] ['id']] = $_row ['@attributes'] ['value'];
                    }
                    array_push($resBody, $coll);
                }
                $res ['body'] = $resBody;
            } else {
                foreach ($arr ['iColl'] as $key => $row) {
                    $res [$row ['id']] = $row ['append'];
                }
            }
        }
        return $res;
    }

    public function getErrors()
    {
        return $this->_errors;
    }

    public function resetErrors()
    {
        $this->_errors = array();
    }

    public function setErrors($error)
    {
        array_push($this->_errors, $error);
        $params = array();
        $params['server'] = $_SERVER;
        $params['post'] = $_POST;
        $params['get'] = $_GET;
        $params['request'] = $_REQUEST;
        $params['raw'] = file_get_contents("php://input");
        file_put_contents(Yii::app()->getRuntimePath() . '/pay_error_' . date("Y_W") . '.log', sprintf("%s %s %s %s", date("Y-m-d H:i:s"), json_encode($error), json_encode($params), PHP_EOL), FILE_APPEND);
    }

}
